﻿using DotNetCommon.Extensions;
using System;
using System.Buffers.Text;
using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text;

namespace DotNetCommon.Serialize;

#region DateOnly & TimeOnly
/// <summary>
/// 专为 .net6 提供
/// </summary>
internal sealed class JsonConverterDateOnly : JsonConverter<DateOnly>
{
    public string Format { get; set; }
    public JsonConverterDateOnly()
    {
        this.Format = "yyyy-MM-dd";
    }
    public JsonConverterDateOnly(string format)
    {
        AssertUtil.NotNull(format);
        Format = format;
    }
    public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateOnly.Parse(reader.GetString());
    }

    public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(Format));
    }
}

/// <summary>
/// 专为 .net6 提供
/// </summary>
internal sealed class JsonConverterTimeOnly : JsonConverter<TimeOnly>
{
    public string Format { get; set; }
    public JsonConverterTimeOnly()
    {
        this.Format = "HH:mm:ss.fffffff";
    }
    public JsonConverterTimeOnly(string format)
    {
        AssertUtil.NotNull(format);
        Format = format;
    }
    public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return TimeOnly.Parse(reader.GetString());
    }

    public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(Format));

    }
}
#endregion

#region Datetime & DateTimeOffset
/// <summary>
/// DateTime转换器
/// </summary>
internal class JsonConverterDatetime : JsonConverter<DateTime>
{
    /// <summary>
    /// 如: "yyyy-MM-dd HH:mm:ss",默认: null
    /// </summary>
    public string Format { get; set; }
    /// <summary>
    /// 使用默认格式,同: <c>System.Text.Json.JsonSerializer.Serialize(obj)</c>
    /// </summary>
    public JsonConverterDatetime() { }
    /// <summary>
    /// 指定格式化字符串
    /// </summary>
    public JsonConverterDatetime(string format)
    {
        Format = format;
    }
    /// <summary>
    /// 自定义反序列化方法
    /// </summary>
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (!reader.TryGetDateTime(out DateTime value))
        {
            value = DateTime.Parse(reader.GetString()!);
        }
        return value;
    }

    /// <summary>
    /// 自定义序列化方法
    /// </summary>
    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        if (Format.IsNotNullOrWhiteSpace())
        {
            writer.WriteStringValue(value.ToString(Format));
        }
        else
        {
            Span<byte> utf8Date = new byte[50];
            bool result = Utf8Formatter.TryFormat(value, utf8Date, out int len, new StandardFormat('O'));
            if (result) writer.WriteStringValue(utf8Date.Slice(0, len));
            else writer.WriteStringValue(value.ToString(Format));
        }
    }
}

/// <summary>
/// DateTimeOffset转换器
/// </summary>
internal class JsonConverterDateTimeOffset : JsonConverter<DateTimeOffset>
{
    /// <summary>
    /// 如: "yyyy-MM-dd HH:mm:ss",默认: null
    /// </summary>
    public string Format { get; set; }
    /// <summary>
    /// 使用默认格式,同: <c>System.Text.Json.JsonSerializer.Serialize(obj)</c>
    /// </summary>
    public JsonConverterDateTimeOffset() { }
    /// <summary>
    /// 指定格式化字符串
    /// </summary>
    /// <param name="format"></param>
    public JsonConverterDateTimeOffset(string format)
    {
        Format = format;
    }
    /// <summary>
    /// 自定义反序列化方法
    /// </summary>
    public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (!reader.TryGetDateTimeOffset(out DateTimeOffset value))
        {
            value = DateTimeOffset.Parse(reader.GetString()!);
        }
        return value;
    }

    /// <summary>
    /// 自定义序列化方法
    /// </summary>
    public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
    {
        if (Format.IsNotNullOrWhiteSpace())
        {
            writer.WriteStringValue(value.ToString(Format));
        }
        else
        {
            Span<byte> utf8Date = new byte[50];
            bool result = Utf8Formatter.TryFormat(value, utf8Date, out int len, new StandardFormat('O'));
            if (result) writer.WriteStringValue(utf8Date.Slice(0, len));
            else writer.WriteStringValue(value.ToString(Format));
        }
    }
}
#endregion

/// <summary>
/// string转换器,解决问题: <br />
/// 123 => "123"<br/>
/// true => "true"
/// </summary>
internal class JsonConverterString : JsonConverter<string>
{
    /// <summary>
    /// 空参构造函数
    /// </summary>
    public JsonConverterString() { }
    /// <summary>
    /// 自定义反序列化方法
    /// </summary>
    public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.None:
            case JsonTokenType.Null:
                return null;
            case JsonTokenType.Number:
                return reader.GetDouble().ToString();
            case JsonTokenType.True:
                return "true";
            case JsonTokenType.False:
                return "false";
            default:
                return reader.GetString();
        }
    }

    /// <summary>
    /// 自定义序列化方法
    /// </summary>
    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value);
    }
}

internal class JsonConverterBool : JsonConverter<bool>
{
    public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.None:
            case JsonTokenType.Null:
                throw new Exception($"无法将 null 反序列化为 bool!");
            case JsonTokenType.Number:
                var d = reader.GetDouble();
                return !(d == 0);
            case JsonTokenType.String:
                var str = reader.GetString();
                if (string.Equals(str, "true", StringComparison.OrdinalIgnoreCase)) return true;
                else if (string.Equals(str, "false", StringComparison.OrdinalIgnoreCase)) return false;
                else throw new Exception($"无法将非 \"true\"或\"false\" 的字符串 转为 bool!");
            case JsonTokenType.True: return true;
            case JsonTokenType.False: return false;
            default: throw new Exception($"无法将 {reader.TokenType} 反序列化为 bool!");
        }
    }

    public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
    {
        writer.WriteBooleanValue(value);
    }
}
internal class NullAbleConverter : JsonConverterFactory
{
    private static ConcurrentDictionary<Type, JsonConverter> _cache = new();
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert.IsNullable();
    }

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        return _cache.GetOrAdd(typeToConvert, () =>
        {
            return (JsonConverter)Activator.CreateInstance(typeof(NullAbleConverter<>).MakeGenericType(typeToConvert));
        });
    }
}

internal class NullAbleConverter<T> : JsonConverter<T>
{
    public NullAbleConverter()
    {
        s_UnderlyingType = Nullable.GetUnderlyingType(typeof(T));
    }
    private Type s_UnderlyingType = null;
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert.IsNullable();
    }
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.Null:
            case JsonTokenType.None:
                return default;
            case JsonTokenType.String:
                var str = reader.GetString();
                if (str.IsNullOrWhiteSpace()) return default;
                return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
            case JsonTokenType.StartArray:
                {
                    if (s_UnderlyingType.IsEnum)
                    {
                        //将 [] 转为 EnumFlag? 的时候转为 null
                        return (T)JsonEnumConverter.ConvertFromReaderWhenArray(ref reader, s_UnderlyingType, typeToConvert);
                    }
                    return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
                }
            default:
                return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
        }
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        if (value == null)
        {
            writer.WriteStringValue("null");
        }
        else
        {
            JsonSerializer.Serialize(writer, value, value.GetType(), options);
        }
    }
}

internal class JsonEnumConverter : JsonConverterFactory
{
    internal static object ConvertFromReaderWhenArray(ref Utf8JsonReader reader, Type typeToConvert, Type oldType)
    {
        var sb = new StringBuilder();
        sb.Append('[');
        var idx = 0;
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndArray)
            {
                sb.Append(']');
                break;
            }
            else if (reader.TokenType == JsonTokenType.String)
            {
                idx++;
                if (idx > 1) sb.Append(',');
                sb.Append('"').Append(reader.GetString()).Append('"');
            }
            else if (reader.TokenType == JsonTokenType.Number)
            {
                idx++;
                if (idx > 1) sb.Append(',');
                sb.Append(reader.GetInt32());
            }
            else throw new Exception($"无法将数组转成枚举({typeToConvert.GetClassFullName()}),除非是简单数组(如:[1,2],[\"close\",\"open\"])!");
        }
        return ObjectExtensions.ConvertToEnum(typeToConvert, oldType, sb.ToString());
    }
    public JsonEnumConverter(bool enum2String)
    {
        this.enum2String = enum2String;
    }
    private static ConcurrentDictionary<Type, JsonConverter> _cache2String = new();
    private static ConcurrentDictionary<Type, JsonConverter> _cache2Numer = new();
    private readonly bool enum2String;

    public sealed override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert.IsEnum;
    }

    public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        var _cache = enum2String ? _cache2String : _cache2Numer;
        return _cache.GetOrAdd(typeToConvert, () =>
        {
            var ctor = typeof(EnumConverter<>).MakeGenericType(typeToConvert).GetConstructor(new Type[] { typeof(bool) });
            return (JsonConverter)ctor.Invoke(new object[] { enum2String });
        });
    }
}

internal sealed class EnumConverter<T> : JsonConverter<T> where T : struct, Enum
{
    public EnumConverter(bool enum2String)
    {
        this.enum2String = enum2String;
    }
    private static readonly TypeCode s_enumTypeCode = Type.GetTypeCode(typeof(T));
    private readonly bool enum2String;

    public override bool CanConvert(Type type)
    {
        return type.IsEnum;
    }

    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.String:
                {
                    var str = reader.GetString();
                    if (Enum.TryParse(typeof(T), str, true, out var ret)) return (T)ret;
                    return (T)ObjectExtensions.ConvertToEnum(typeToConvert, typeToConvert, str);
                }
            case JsonTokenType.Number:
                {
                    switch (s_enumTypeCode)
                    {
                        case TypeCode.Int32:
                            {
                                if (reader.TryGetInt32(out var value8))
                                {
                                    return Unsafe.As<int, T>(ref value8);
                                }
                                break;
                            }
                        case TypeCode.UInt32:
                            {
                                if (reader.TryGetUInt32(out var value4))
                                {
                                    return Unsafe.As<uint, T>(ref value4);
                                }
                                break;
                            }
                        case TypeCode.UInt64:
                            {
                                if (reader.TryGetUInt64(out var value6))
                                {
                                    return Unsafe.As<ulong, T>(ref value6);
                                }
                                break;
                            }
                        case TypeCode.Int64:
                            {
                                if (reader.TryGetInt64(out var value2))
                                {
                                    return Unsafe.As<long, T>(ref value2);
                                }
                                break;
                            }
                        case TypeCode.SByte:
                            {
                                if (reader.TryGetSByte(out var value7))
                                {
                                    return Unsafe.As<sbyte, T>(ref value7);
                                }
                                break;
                            }
                        case TypeCode.Byte:
                            {
                                if (reader.TryGetByte(out var value5))
                                {
                                    return Unsafe.As<byte, T>(ref value5);
                                }
                                break;
                            }
                        case TypeCode.Int16:
                            {
                                if (reader.TryGetInt16(out var value3))
                                {
                                    return Unsafe.As<short, T>(ref value3);
                                }
                                break;
                            }
                        case TypeCode.UInt16:
                            {
                                if (reader.TryGetUInt16(out var value))
                                {
                                    return Unsafe.As<ushort, T>(ref value);
                                }
                                break;
                            }
                    }
                    throw new Exception($"无法从 {JsonTokenType.Number} 转为枚举({typeToConvert.GetClassFullName()})!");
                }
            case JsonTokenType.StartArray:
                return (T)JsonEnumConverter.ConvertFromReaderWhenArray(ref reader, typeToConvert, typeToConvert);
            default:
                throw new Exception($"无法从 {reader.TokenType} 转为枚举({typeToConvert.GetClassFullName()})!");
        }
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        if (enum2String)
        {
            writer.WriteStringValue(value.ToString());
        }
        else
        {
            switch (s_enumTypeCode)
            {
                case TypeCode.Int32:
                    writer.WriteNumberValue(Unsafe.As<T, int>(ref value));
                    break;
                case TypeCode.UInt32:
                    writer.WriteNumberValue(Unsafe.As<T, uint>(ref value));
                    break;
                case TypeCode.UInt64:
                    writer.WriteNumberValue(Unsafe.As<T, ulong>(ref value));
                    break;
                case TypeCode.Int64:
                    writer.WriteNumberValue(Unsafe.As<T, long>(ref value));
                    break;
                case TypeCode.Int16:
                    writer.WriteNumberValue(Unsafe.As<T, short>(ref value));
                    break;
                case TypeCode.UInt16:
                    writer.WriteNumberValue(Unsafe.As<T, ushort>(ref value));
                    break;
                case TypeCode.Byte:
                    writer.WriteNumberValue(Unsafe.As<T, byte>(ref value));
                    break;
                case TypeCode.SByte:
                    writer.WriteNumberValue(Unsafe.As<T, sbyte>(ref value));
                    break;
                default:
                    throw new Exception($"无法将 {s_enumTypeCode} 序列化!");
            }
        }
    }

    public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.String:
                {
                    var str = reader.GetString();
                    if (Enum.TryParse(typeof(T), str, true, out var ret)) return (T)ret;
                    return (T)ObjectExtensions.ConvertToEnum(typeToConvert, typeToConvert, str);
                }
            case JsonTokenType.Number:
                switch (s_enumTypeCode)
                {
                    case TypeCode.Int32:
                        {
                            if (reader.TryGetInt32(out var value8))
                            {
                                return Unsafe.As<int, T>(ref value8);
                            }
                            break;
                        }
                    case TypeCode.UInt32:
                        {
                            if (reader.TryGetUInt32(out var value4))
                            {
                                return Unsafe.As<uint, T>(ref value4);
                            }
                            break;
                        }
                    case TypeCode.UInt64:
                        {
                            if (reader.TryGetUInt64(out var value6))
                            {
                                return Unsafe.As<ulong, T>(ref value6);
                            }
                            break;
                        }
                    case TypeCode.Int64:
                        {
                            if (reader.TryGetInt64(out var value2))
                            {
                                return Unsafe.As<long, T>(ref value2);
                            }
                            break;
                        }
                    case TypeCode.SByte:
                        {
                            if (reader.TryGetSByte(out var value7))
                            {
                                return Unsafe.As<sbyte, T>(ref value7);
                            }
                            break;
                        }
                    case TypeCode.Byte:
                        {
                            if (reader.TryGetByte(out var value5))
                            {
                                return Unsafe.As<byte, T>(ref value5);
                            }
                            break;
                        }
                    case TypeCode.Int16:
                        {
                            if (reader.TryGetInt16(out var value3))
                            {
                                return Unsafe.As<short, T>(ref value3);
                            }
                            break;
                        }
                    case TypeCode.UInt16:
                        {
                            if (reader.TryGetUInt16(out var value))
                            {
                                return Unsafe.As<ushort, T>(ref value);
                            }
                            break;
                        }
                }
                throw new Exception($"无法从 {JsonTokenType.Number} 转为枚举({typeToConvert.GetClassFullName()})!");
            case JsonTokenType.PropertyName:
                {
                    var str = reader.GetString();
                    if (Enum.TryParse(typeof(T), str, true, out var ret)) return (T)ret;
                    return (T)ObjectExtensions.ConvertToEnum(typeToConvert, typeToConvert, str);
                }
            case JsonTokenType.StartArray:
                return (T)JsonEnumConverter.ConvertFromReaderWhenArray(ref reader, typeToConvert, typeToConvert);
            default:
                throw new Exception($"无法从 {reader.TokenType} 转为枚举({typeToConvert.GetClassFullName()})!");
        }
    }

    public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WritePropertyName(value.ToString());
    }
}
